package models

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"github.com/zeromicro/go-zero/core/logx"
	"net/http"
	"strconv"
	"sync"
	"time"
)

/**
 * @Author: way lee
 * @Date: 2024/9/23 08:14
 * @Desc:
 */

type LogEntry struct {
	CreatedTime   string `db:"created_at" json:"createdTime,omitempty"`
	ClusterID     string `db:"cluster_id" json:"clusterID"`
	TaskID        string `db:"task_id" json:"taskID"`
	Component     string `db:"component" json:"component"`
	ComponentRole string `db:"component_role" json:"componentRole"`
	StepNumber    string `db:"step_number" json:"stepNumber"`
	LogLevel      string `db:"log_level" json:"logLevel"`
	Message       string `db:"message" json:"message,omitempty"`
	NodeHost      string `db:"node_host" json:"nodeHost"`
}

type LogManager struct {
	logChannel chan LogEntry
	url        string
	wg         sync.WaitGroup
	logs       []LogEntry
	client     *http.Client
	mu         sync.Mutex
	ticker     *time.Ticker
	ctx        context.Context
	cancel     context.CancelFunc
}

// NewLogManager 初始化日志管理器
func NewLogManager(bufferSize int, url string) *LogManager {
	ctx, cancel := context.WithCancel(context.Background())
	lm := &LogManager{
		logChannel: make(chan LogEntry, bufferSize),
		url:        fmt.Sprintf("%s/v1/log/add_deploy", url),
		logs:       make([]LogEntry, 0),
		client: &http.Client{
			Timeout: 10 * time.Second,
		},
		ticker: time.NewTicker(5 * time.Second),
		ctx:    ctx,
		cancel: cancel,
	}
	lm.wg.Add(1)
	go lm.processLogChannel()
	return lm
}

// SendLog 将日志消息发送到日志通道
func (lm *LogManager) SendLog(clusterID, nodeHost string, step int, logLevel, message, component, componentRole string) {
	currentTime := time.Now().Format("2006-01-02 15:04:05.000")

	select {
	case lm.logChannel <- LogEntry{
		ClusterID:     clusterID,
		TaskID:        "agent_" + nodeHost,
		NodeHost:      nodeHost,
		CreatedTime:   currentTime,
		Component:     component,
		ComponentRole: componentRole,
		Message:       message,
		StepNumber:    strconv.Itoa(step),
		LogLevel:      logLevel,
	}:
	case <-lm.ctx.Done():
		return
	}
}

// processLogChannel 处理日志通道中的日志条目并批量发送到API
func (lm *LogManager) processLogChannel() {
	defer func() {
		lm.flushLogs()
		lm.wg.Done()
	}()

	for {
		select {
		case <-lm.ctx.Done():
			return
		case <-lm.ticker.C:
			lm.flushLogs()
		case logEntry, ok := <-lm.logChannel:
			if !ok {
				return
			}
			lm.handleLogEntry(logEntry)
		}
	}
}

// handleLogEntry 处理单个日志条目
func (lm *LogManager) handleLogEntry(logEntry LogEntry) {
	switch logEntry.LogLevel {
	case "INFO":
		logx.Infof("Step %s 主机: %s, %s", logEntry.StepNumber, logEntry.NodeHost, logEntry.Message)
	case "ERROR":
		logx.Errorf("Step %s 主机: %s, %s", logEntry.StepNumber, logEntry.NodeHost, logEntry.Message)
	case "DEBUG":
		logx.Debugf("[%s] Step %s 主机: %s, %s", logEntry.LogLevel, logEntry.StepNumber, logEntry.NodeHost, logEntry.Message)
	default:
		logx.Infof("[%s] Step %s 主机: %s, %s", logEntry.LogLevel, logEntry.StepNumber, logEntry.NodeHost, logEntry.Message)
	}

	lm.mu.Lock()
	lm.logs = append(lm.logs, logEntry)
	lm.mu.Unlock()
}

// flushLogs 批量发送日志到API
func (lm *LogManager) flushLogs() {
	lm.mu.Lock()
	defer lm.mu.Unlock()

	if len(lm.logs) == 0 {
		return
	}
	logx.Infof("部署日志写入数据库...")
	lm.sendLogsToAPI(lm.logs)
	lm.logs = nil
}

// sendLogsToAPI 批量发送日志到指定的API端点
func (lm *LogManager) sendLogsToAPI(logs []LogEntry) {
	payload := struct {
		DeployLogs []LogEntry `json:"deployLogs"`
	}{
		DeployLogs: logs,
	}

	data, err := json.Marshal(payload)
	if err != nil {
		logx.Errorf("Failed to marshal logs: %v", err)
		return
	}

	req, err := http.NewRequest("POST", lm.url, bytes.NewBuffer(data))
	if err != nil {
		logx.Errorf("Failed to create new request: %v", err)
		return
	}
	req.Header.Set("Content-Type", "application/json")

	maxRetries := 3
	for i := 0; i < maxRetries; i++ {
		resp, err := lm.client.Do(req)
		if err != nil {
			logx.Errorf("Failed to send logs to API (attempt %d/%d): %v", i+1, maxRetries, err)
			time.Sleep(2 * time.Second)
			continue
		}
		defer resp.Body.Close()

		if resp.StatusCode == http.StatusOK {
			return
		}

		logx.Errorf("Failed to send logs, response code: %d (attempt %d/%d)", resp.StatusCode, i+1, maxRetries)
		time.Sleep(2 * time.Second)
	}
}

// Close 关闭日志通道并等待所有日志处理完成
func (lm *LogManager) Close() {
	lm.cancel()
	close(lm.logChannel)
	lm.wg.Wait()
	lm.ticker.Stop()
}
